// Copyright 2009 Jan Hosang <jan.hosang@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gomake

import (
    "io/ioutil"
    "os"
    "go/parser"
    "go/ast"
    "strings"
    "log"
    "path"
    "gomake/depgraph"
    "fmt"
)

type gomakeConfig struct {
    src, obj, pkg string
    target string
}
var config gomakeConfig

func Make(srcDir, objDir, pkgDir, target string, onlyScan bool, targets []string) {
    config = gomakeConfig{src: path.Clean(srcDir), obj: path.Clean(objDir), pkg: path.Clean(pkgDir), target: target}
    config.pkg = path.Join(config.pkg, os.Getenv("GOOS")+"_"+os.Getenv("GOARCH"))
    os.MkdirAll(config.pkg, 0755)
    
    files, err := collectGoFiles(config.src)
    if err != nil {
        log.Exit(err)
    }
    err = getDetails(files)
    if err != nil {
        log.Exit(err)
    }
    
    pkgs := newGoPkgsFromFiles(files)
    
    depGraph := depgraph.NewDependencyGraph()
    pkgs.addToDepGraph(depGraph)
    
    clean := NewCleanTarget(pkgs)
    depGraph.AddDependency(clean)
    nuke := NewNukeTarget(pkgs)
    depGraph.AddDependency(nuke)
    
    if onlyScan {
        fmt.Print(depGraph.String())
    } else {
        depGraph.Build(targets)
        printBuiltBinaries()
    }
}

// Walks trough a directory and recursively through all it's subdirectories
// and collects all filenames ending with ".go" and their modification
// time.
func collectGoFiles(dirname string) (*goFiles, os.Error) {
    file, err := os.Open(dirname, os.O_RDONLY, 0)
    if err != nil {
        return nil, err
    }
    
    dirs, err := file.Readdir(-1)
    if err != nil {
        return nil, err
    }
    
    files := newGoFiles()
    for _, dir := range dirs {
        if dir.IsRegular() && strings.HasSuffix(dir.Name, ".go") {
            filename := path.Join(dirname, dir.Name)
            (*files)[filename] = newGoFile(dir.Mtime_ns)
        } else if dir.IsDirectory() {
            newFiles, err := collectGoFiles(path.Join(dirname, dir.Name))
            if err != nil {
                return nil, err
            }
            files.extend(newFiles)
        }
    }
    err = file.Close()
    
    return files, err
}

func getDetails(files *goFiles) os.Error {
    for fileName, fileObj := range *files {
        // XXX TODO check if we have to parse that file
        pkg, imports, err := getPkgImports(fileName)
        if err != nil {
            return err
        }
        fileObj.pkg = pkg
        deps := make([]string, len(imports))
        j := 0
        for imp, _ := range imports {
            deps[j] = imp
            j += 1
        }
        fileObj.deps = deps
    }
    return nil
}

func getPkgImports(filename string) (string, map[string] bool, os.Error) {
    src, err := ioutil.ReadFile(filename)
    if err != nil {
        return "", nil, err
    }
    
    prog, err := parser.ParseFile(filename, src, parser.ImportsOnly)
    if err != nil {
        return "", nil, err
    }
    
    pkgName := prog.Name.Value
    imports := make(map[string]bool)
    for _, decl := range prog.Decls {
        if importDecl, ok := decl.(*ast.GenDecl); ok {
            for _, spec := range importDecl.Specs {
                if importSpec, ok := spec.(*ast.ImportSpec); ok {
                    for _, path := range importSpec.Path {
                        path := string(path.Value);
                        path = path[1:len(path)-1]
                        imports[path] = true;
                    }
                }
            }
        }
    }
    
    return pkgName, imports, nil
}

func max(a, b uint64) uint64 {
    if a <= b {
        return b
    }
    return a
}
